Choix de personnages inclusif pour un visual novel

Introduction

Dans cet article, je présente mes réflexions pour réaliser un menu de choix de personnages pour un projet de visual novel inclusif dans lequel il serait possible de sélectionner un protagoniste selon une grille genrée et une teinte de peau variées permettant une identification large pour les joueurs, et en restant reproductible et abordable pour les illustrateurs.



Concernant le genre, la solution envisagée reposera sur l’effort des illustrateurs en proposant deux alternatives visuels aux passing masc et fem (puis un rédécoupage sur les zones du corps permettant 2³ = 8 variantes. Cela restera donc une offre limitée en ne permettant pas d’exprimer le genre sous la forme d’un spectre et présentera des limites pour les expressions de genre non binaires. Ce type de compromis est assez fréquent pour des jeux indépendants ayant peu de moyens et il reste tout de même assez exigeant pour les illustrateurs. D’un point de vue artistique, il sera également possible de concevoir le protagoniste de manière à ce qu’il soit raisonnablement éloigné des stéréotypes de genre en faisant le choix d’une légère androgynie.

Concernant le spectre des traits d’origine, nous faison également un compromis entre l’expressivité proposée et la somme de somme de travail pour les illustrateurs / développeurs en se concentrant sur un choix de couleur de peau / cheveux / yeux. Il n’y aura donc pas de choix de traits phénotypes (traits, lèvres, etc.) qui multiplieraient par autant de paramètres et de scènes le nombre d’illustrations à produire. Un personnage aux cheveux courts permet non seulement de réduire à peu de frais le travail en se recentrant sur un stéréotype de genre consensuel et en omettant le choix de texture des cheveux.

Objectifs

Réaliser un « proof of concept » de menu intégrable dans un environnement Renpy. Les moyens alloués à ce projet étant assez modestes, les solutions apportées devront essayer de se montrer les plus expressives possible en limitant l’impact sur le quantité de travail nécessaire.

L’objectif principal est de proposer une manière procédurale de faire varier la couleur de peau de manière expressive et qui ne se limite à un nombre fini de choix.

Premières expérimentations sur photoshop

Dans cette partie, je présente les expériences réalisées pour gérer un personnage à la couleur de peau choisie initialement dans un menu. Il s’agit d’une discussion technique qui amènera des considération éthiques par la suite.

Dans un premier temps j’ai imaginé un process utilisant des modes de superposition de calques et l’utilisation de courbes de transfert de couleurs sous photoshop permettant de passer d’une image noir et blanc à une image colorisée. C’est le point de départ pour vérifier la faisabilité d’une illustration avec un rendu de couleur de peau paramétrable qui soit convaincant et ne jure pas avec le reste du jeu (décors et autres personnages). Une fois ce process mis en place sous photoshop (outil des illustrateurs), je vais chercher à le reproduire de manière procédurale sous python.

L’objectif est d’être capable d’appliquer ces calques et ces filtres dans l’environnement Renpy. Or, je me heurte à une limitation car les librairies permettant de gérer en temps réel du traitement d’image en python (Pillow et numpy) ne sont pas intégrée à Renpy. J’ai donc exploré deux voies alternative : faire sans ces librairies (les performances étaient catastrophiques) et lancer depuis Renpy un programme réalisé en python incluant ces librairies (un peu bricolé, mais fonctionnel).

Une troisième solution non-envisagée ici serait de recompiler Renpy avec les libairires nécessaires, mais je manque de connaissances pour l’instant et l’objectif principal est de faire un proof of concept.

Process de colorisation (détail photoshop)

L’idée sera de partir d’une image en noir et blanc et de la coloriser avec un filtre (courbes de transfers), des masques (séparer les zones de l’image) et un calques (gérer les tonalités rouges de la peau liées à la vascularisation, nez, joues, lèvres).

Je suis parti d’une de mes recherches de style pour le personnage (s’inspirant fortement de Dean Yeagle), initialement en couleurs, mais que je passe en noir et blanc. Cela me permettra de « reconstruire » la couleur via mon process et de comparer le résultat à la colorisation initiale pour tester la qualité du process.

Le filtre « courbe de transfert » permet d’associer à certaines valeurs de gris s’étalant entre 0 (pur noir) et 100 % (pur blanc), des couleurs que l’on choisit. L’objectif sera de tenir compte du choix utilisateur de couleur de peau pour « recoloriser » l’avatar noir et blanc. Un masque monochrome permet de séparer la zone de peau du reste du personnage.

Le « gradient » obtenu forme un continuum de teintes de peaux allant des zones d’occlusion (ombre maximum) jusqu’aux highlights (éclairage maximal). Le résultat obtenu est assez « linéaire ». Il ne tient pas compte des variations de teintes rouges dans un visage dues à la vascularisation plus importante au niveau du nez, des joues, des lèvres. Pour cela on ajoute un calque de « rouges » qui vient compléter l’image.

Le résultat obtenu reste satisfaisant et proche de l’image initiale. Des process similaires peuvent être appliqués pour les iris et pour la pilosité avec à chaque fois un filtre et une courbe de transfert adaptée pouvant correspondre à un choix utilisateur.

Quels type de menu pour choisir une couleur ?

Concernant les yeux et les cheveux, le continuum « réaliste » est évidemment multipolaire (yeux bleus, verts, bruns par exemple). Concernant la couleur de peau, un simple « slider » présente non seulement des limitations en terme d’esthétique, de réalisme et éthique. Un slider qui implique de facto une sorte de hiérarchie peau-sombre / peau-claire serait pour le moins malvenue et omettrait de facto des teintes de peau extrêmements variées. Les photos de sites de cosmétique s’intéressant aux différents types de peau ont été une première base de références utile afin de comparer les résultats de l’outil à des exemples variés et représentatifs. Il est important de pouvoir tenir compte non seulement du fait que la peau soit sombre ou claire, mais aussi de sa chaleur afin de restituer des peaux ambrées , rosées, olivatres, etc. Cette approche est en faveur d’un menu « multipolaire ».

Une autre ressource importante, plus scientifique, est celle de la classification des « mélanotypes » utilisée en dermatologie (7 catégories tenant compte de la teneur en mélanine) ou la classification de Fitzpatrick (6 classes évaluées selon la réaction de la peau au soleil).

Ces deux classification restent couvrent les teintes de peau selon des caractères mesurables particuliers. Il en résulte un échelle qui ne convient pas vraiment à la richesse des teintes que l’on souhaite voir exprimer par le menu du jeu et peut sembler réductrice du point de vue de l’expression personnelle.

Le choix sera donc fait de partir sur la réalisation de menus « multipolaires ». Autrement dit une surface polygonales régulière à n côté dont chaque sommet correspond à un pôle. Pour n=3, ça sera un triangle équilatéral, n=4 un carré, etc. On trouve par exemple ce genre de sélecteur dans l’outil de corpulence des manequins de Clip Studio Paint (où les 4 pôles permettent de tenir compte des zones de stockage des graisses différentes selon les types de corps).

Cela permettra de créer un menu expressif, adaptable également aux autres choix de couleurs avec un nombre de pôles variables (yeux et cheveux), mais également d’éviter l’écueil du « slider hiérarchique » :(

Programmation

Cas simple à 2 couleurs

Dans un premier temps, le code réalise un menu de choix simple de type Slider avec un choix de peau à 2 pôles entre deux couleurs de référence COULEUR_PEAU_SOMBRE et COULEUR_PEAU_CLAIRE constantes. La couleur choisie par l’utilisateur COULEUR_PEAU est calculée proportionnellement à la position du slider sur sa barre. Les couleurs sont données en RGB à partir de valeurs observées sur photoshop.

Les calques sont importés sous forme de 3 objets Image : original_img l’image originale en dégradés de gris, mask_img le masque monochrome qui délimite la zone à colorisée et red_overlay le calque des rouges. Pour des raisons de performance, nous utilisons la librairie numpy et convertissons les images en np.array pour gérer les calculs en temps réel.

La courbe de transfert est récupérée depuis photoshop et définie à l’aide du tableau GRADIENT_MAP qui fait correspondre aux pourcentages de gris (ici 0, 51, 133, 186, 224 et 255) aux couleurs choisies. Ces valeurs correspondent bien sûr à l’image « moyenne » ou plutôt l’image originale colorisée par défaut.

A chaque choix d’une couleur, nous devons donc recalculer un nouveau GRADIENT_MAP intelligemment, puis actualiser l’image dans la zone définie par le masque en tenant compte de ce nouveau gradient.

Le nouveau GRADIENT_MAP donnant une correspondance niveaux de gris – couleurs interpolées, nous choisissons arbitrairement de faire correspondre la couleur du slider au gris médian 128 (50 % de gris). Les valeurs extrêmes correspondent toujours à du pur blanc et du pur noir.

Cas à 3 couleurs

La partie précédente n’est qu’une étape vers une généralisation. On l’adapte pour afficher dynamiquement un triangle de choix à partir de 3 couleurs de base PEAU_CLAIRE, PEAU_SOMBRE, PEAU_AMBRE et actualiser l’image via le calcul d’un nouveau GRADIENT_MAP de manière analogue à la partie précédente.

La seule différence structurelle en passant à 3 points se trouve au niveau de l’interpolation où il est nécessaire de calculer les coordonnées baricentriques (alpha, beta, gamma) du point dans le triangle colorPicker pour retrouver la couleur choisie à partir de la position du curseur.

Généralisation

Le colorPicker peut être intéressant également pour les yeux, les cheveux, etc. Il est donc naturel de designer un outil où le nombre de pôles dans la couleur peut être un nombre entier n quelconque. De plus, chemin faisant, il nous a semblé naturel de proposer un sélecteur polygonal ou circulaire (comme pour le cercle chromatique).

Le polygone s’obtient par recollement des triangles collorées selon la méthode précédente. Le disque s’obtient par « dilatation » du polygone dans son cercle circonscrit. Nous passons les détails techniques.

Nous avons finalement optimisé le code en traduisant les calculs sur les coordonnées des tableaux numpy en utilisant uniquement les méthodes implémentées dans numpy (sans quoi les ralentissements sont vraiment perceptibles). Nous avons également utilisé des fonctions de filtre afin de répartir les couleurs sur la surface du colorPicker avec plus ou moins de douceur. Enfin, nous ajoutons un pôle de couleur facultatif au centre du polygone/disque.

Les colorPicker sont maintenant des objets instanciables, il est donc possible de les multiplier en leur associant les attributs nécessaires comme l’image initiale, le masque définissant la zone, les n couleurs principales, le gradient définissant la courbe de transfert et les différentes attributs tels que les positions, tailles et options de menu.

Dans l’exemple ci-dessus on observe 3 menus avec différentes options pour les choix de couleur de peau, des yeux et de cheveux. Pour les yeux et les cheveux, les couleurs sont arbitraires et criardes, il est nécessaire de bien les choisir au départ en tenant compte de la position initiale médiane du curseur supposé correspondre à un gris moyen de 50 %. Ce dernier point devrait être amélioré pour retirer ce tracas des mains de l’illustrateur.

Conclusion

Le menu est fonctionnel et léger du côté artiste qui, bien briefé, n’a pas à se soucier de problèmes techniques en dehors de garder séparé le calque des rouges et de produire les masques de peaux, cheveux et yeux. Il devrai également s’en tenir à la palette initiale lorsqu’il colorisera son personnage avant de le passer en noir et blanc. Un étalonnage pourrait être nécessaire par la suite pour conserver une cohrérence chromatique (une bonne répartition des gris). Les fonctions permettent de calculer après validation du choix des couleurs, le calcul des images faisant intervenir le protagoniste. Renpy n’embarque pas les bibliothèques nécessaires au menu, nous avons donc compilé le menu séparément en un exécutable qui est appelé par Renpy. Il sera probablement nécessaire de compiler une version de Renpy avec ces librairies.